import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { defer, of, map, concatMap, delay, firstValueFrom } from "rxjs";
import * as puppeteer from "puppeteer"
import axios from "axios";
import * as cheerio from "cheerio"
import * as moment from "moment"
import { DbService } from "@/db/db.service";
import { resolve } from "path";
import IGlobal from "@/services/global.service";
import IHelper from "@/services/helper.service";
import { IWonewSer } from "@/typing";
import ILogger from "@/services/logger.service";
import { AccountService } from "@/modules/account/account.service";

@Injectable()
export class BiliUserActionHookService {
  logPrefix: string = ""
  started: boolean = false
  loopList: IWonewSer.BiliHookVideoItem[]  = []
  browser: puppeteer.Browser | undefined = undefined
  homePage: puppeteer.Page | undefined = undefined
  constructor(
    private readonly dbService: DbService,
  ) {}

  getCachedKey(type: string, accountId: number): string {
    return `hook_user_action_${type}_${accountId}`
  }
  
  log(message: string) {
    ILogger.info(`${this.logPrefix} ${message}`)
  }

  async fire(accountId: number, logPrefix?: string) {
    this.logPrefix = logPrefix || "[BiliUserActionHookService 养号]"
    this.log("fire!")
    const cachedKey = this.getCachedKey("service", accountId)
    if (IGlobal.store[cachedKey]) {
      this.log("Service is running! return!")
      return {status: false, message: "Service is running!"}
    }
    if (!await this.isServiceAvailable(accountId)) {
      this.log("Service not available! return!")
      return {status: false, message: "Service not available!"}
    }
    this.log("Service starting!")
    this.flushPage(accountId)
    return {status: true, message: "Service start!"}
  }

  async getCookie(accountId: number): Promise<puppeteer.Cookie[]> {
    let node = await this.dbService.accountRepository.findOneByOrFail({id: accountId})
    if (!node.cookie) {
      throw new HttpException(`Account cookie not exist`, HttpStatus.BAD_REQUEST)
    }
    return JSON.parse(node.cookie)
  }

  async getBrowser(accountId: number): Promise<puppeteer.Browser> {
    const cachedKey = this.getCachedKey("browser", accountId)
    let cachedBrowser: puppeteer.Browser | undefined = IGlobal.store[cachedKey]
    if (!cachedBrowser) {
      cachedBrowser = await puppeteer.launch({
        headless: true,
        defaultViewport: {
          width: 1280, height: 720
        },
        protocolTimeout: 10 * 60 * 1000,
        args: ['--no-sandbox','--disable-setuid-sandbox', "--window-size=1280,720", "--mute-audio"],
      })
      IGlobal.store[cachedKey] = cachedBrowser
    }
    return cachedBrowser
  }

  async getHomePage(accountId: number, browser: puppeteer.Browser): Promise<puppeteer.Page> {
    const cachedKey = this.getCachedKey("home_page", accountId)
    let page: puppeteer.Page | undefined = IGlobal.store[cachedKey]
    if (!page) {
      page = await browser.newPage()
      const cookies = await this.getCookie(accountId)
      //设置cookie
      for(let i = 0; i < cookies.length; i++){
        await page.setCookie(cookies[i])
      }
      await page.goto("https://www.bilibili.com/")
      await page.evaluate(() => {
        localStorage.setItem("recommend_auto_play", "close")
      })
      IGlobal.store[cachedKey] = page
    }
    return page
    
  }

  async isServiceAvailable(accountId: number, destroy?: boolean): Promise<boolean> {
    let accountAvailable = false
    try {
      const account = await this.dbService.accountRepository.findOneByOrFail({id: accountId})
      accountAvailable = account.hook_user_action === 1
    } catch(e) {}
    const timeAvailable = IHelper.hookTimeAvailable()
    const serviceAvailable = accountAvailable && timeAvailable
    if (!serviceAvailable && destroy) {
      const serviceKey = this.getCachedKey("service", accountId)
      const browserKey = this.getCachedKey("browser", accountId)
      const homepageKey = this.getCachedKey("home_page", accountId)
      IGlobal.store[serviceKey] = undefined
      if (IGlobal.store[browserKey]) {
        await IGlobal.store[browserKey].close().catch(e => e)
      }
      IGlobal.store[browserKey] = undefined
      IGlobal.store[homepageKey] = undefined
      this.log("Service destroy!")
    }
    return serviceAvailable
  }

  async flushPage(accountId: number) {
    if (!await this.isServiceAvailable(accountId, true)) return
    this.log("Start flushPage!")
    IGlobal.store[this.getCachedKey("service", accountId)] = this
    try {
      const browser = await this.getBrowser(accountId)
      const homePage = await this.getHomePage(accountId, browser)
      const refreshButton = await homePage.waitForSelector(".feed-roll-btn")
      await refreshButton.click()
      this.log("刷新数据!")
      const res = await homePage.waitForResponse(response => {
        return response.url().includes("https://api.bilibili.com/x/web-interface/wbi/index/top/feed/rcmd")
      })
      const resJson = await res.json()
      if (!resJson?.data?.item?.[0]?.bvid) {
        throw new HttpException("", HttpStatus.BAD_REQUEST)
      }
      this.log("刷新数据成功!")
      this.loopList = resJson?.data?.item.filter((e: IWonewSer.BiliHookVideoItem) => e.duration > 0 && e.duration < 6 * 60)
      await this.loop(browser, homePage, accountId)
      if (!this.started) {
        this.started = true
        this.log("Service started!")
      }
    } catch (e) {}
    
    this.log("End flushPage!")
    await this.flushPage(accountId)
  }

  async loop(browser: puppeteer.Browser, homePage: puppeteer.Page, accountId: number) {
    for (let i in this.loopList) {
      await this.viewPerItem(browser, homePage, accountId, this.loopList[i])
    }
  }

  async viewPerItem(browser: puppeteer.Browser, homePage: puppeteer.Page, accountId: number, item: IWonewSer.BiliHookVideoItem) {
    if (!await this.isServiceAvailable(accountId)) {
      throw new HttpException("Service unavailable.", HttpStatus.BAD_REQUEST)
    }
    // @ts-ignore
    const duration = moment.duration(item.duration, "seconds").format("HH:mm:ss", {trim: false})
    this.log(`hook user action ${accountId} 开始播放 item ${item.bvid} ${item.title} duration: ${duration}` )
    const videoItem = await homePage.waitForSelector(`.feed-card .bili-video-card__image--link[href="https://www.bilibili.com/video/${item.bvid}"]`)
    await videoItem.click()
    const target = await browser.waitForTarget(t => t.url().includes(item.bvid))
    const newPage = await target.page()
    await newPage.waitForSelector(".bpx-player-ending", {timeout: 10 * 60 * 1000})
    const likeBtn = await newPage.$(".video-like")
    await likeBtn.click()
    await this.sleep(2000)
    await newPage.close()
    this.log(`播放完成 item ${item.bvid} ${item.title}`)
  }

  sleep(time: number): Promise<number> {
    return new Promise<number>(resolve => {
      setTimeout(() => {
        resolve(1)
      }, time)
    })
  }
}